package com.yanwq.pulltorefreshview.view;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;

/**
 * 作者：yanweiqiang。
 * 日期：2016/5/3 0003。
 */
public class PullToRefreshLayout extends RelativeLayout {
    private final float fraction = 2.5f;

    public enum PullMode {
        INTERCEPT, DISPATCH
    }

    private PullMode pullMode;

    /**
     * 拉刷新，拦截和非拦截两种，区别在于拦截刷新在操作时其子view不会收到任何影响，非拦截刷新子view可以获得触摸事件
     *
     * @param pullMode
     */
    public void setPullMode(PullMode pullMode) {
        this.pullMode = pullMode;
    }

    private PullToRefresh pullToRefresh;
    private PullToRefreshListener pullToRefreshListener;
    private boolean ptrListenerSet;
    private PullToLoad pullToLoad;
    private PullToLoadListener pullToLoadListener;
    private boolean ptlListenerSet;
    private ScrollDetector scrollDetector;

    private ScrollDetector.TheEndCallback theEndCallback = new ScrollDetector.TheEndCallback() {
        @Override
        public void end() {
            if (pullToLoad.getStatus() == PullToLoad.Status.IDLE) {
                startLoad();
            }
        }
    };

    public void setPullToRefreshListener(PullToRefreshListener pullToRefreshListener) {
        ptrListenerSet = false;
        this.pullToRefreshListener = pullToRefreshListener;
    }

    public void setPullToLoadListener(PullToLoadListener pullToLoadListener) {
        ptlListenerSet = false;
        this.pullToLoadListener = pullToLoadListener;
    }

    public PullToRefreshLayout(Context context) {
        super(context);
        init();
    }

    public PullToRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        pullMode = PullMode.INTERCEPT;
    }

    public void enableAutoLoad() {
        scrollDetector.setTheEndCallback(theEndCallback);
    }

    public void disableAutoLoad() {
        scrollDetector.setTheEndCallback(null);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        for (int i = 0; i < getChildCount(); i++) {
            if (getChildAt(i) instanceof PullToRefresh) {
                pullToRefresh = (PullToRefresh) getChildAt(i);

                if (pullToRefresh != null) {
                    pullToRefresh.idle();
                }
            } else if (getChildAt(i) instanceof PullToLoad) {
                pullToLoad = (PullToLoad) getChildAt(i);

                if (pullToLoad != null) {
                    pullToLoad.idle();
                }
            } else if (getChildAt(i) instanceof ScrollDetector) {
                scrollDetector = (ScrollDetector) getChildAt(i);
            }
        }
    }

    private float downY;
    private float ptrDownY;
    private float ptlDownY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (pullMode == PullMode.INTERCEPT) {
            return detectPullToRefresh(ev) || detectPullToLoad(ev) || super.onInterceptTouchEvent(ev);
        }

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (pullMode == PullMode.DISPATCH) {
            pullToRefresh(ev);
            pullToLoad(ev);
            return super.dispatchTouchEvent(ev);
        } else {
            return super.dispatchTouchEvent(ev);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (pullMode == PullMode.INTERCEPT) {
            actionPullToRefresh(ev);
            actionPullToLoad(ev);
        }
        return super.onTouchEvent(ev);
    }

    private boolean pullToRefresh(MotionEvent ev) {
        if (pullToRefresh == null) {
            return false;
        }

        if (!ptrListenerSet) {
            pullToRefresh.setListener(pullToRefreshListener);
            ptrListenerSet = true;
        }

        boolean flag = (scrollDetector.theTop() && pullToRefresh.getStatus() == PullToRefresh.Status.IDLE)
                || pullToRefresh.getStatus() == PullToRefresh.Status.PULL;

        if (flag) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    ptrDownY = ev.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (ptrDownY == 0) {
                        ptrDownY = ev.getRawY();
                    }

                    float distance = ev.getRawY() - ptrDownY;

                    if (distance > 0) {
                        distance = distance > 0 ? distance : 0;
                        pullToRefresh.pull((int) (distance / fraction));
                        ((View) scrollDetector).setEnabled(false);
                    } else {
                        pullToRefresh.pull(0);
                        ((View) scrollDetector).setEnabled(true);
                        return false;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    distance = ev.getRawY() - ptrDownY;
                    distance = distance > 0 ? distance : 0;
                    pullToRefresh.release((int) (distance / fraction));
                    ((View) scrollDetector).setEnabled(true);
                    ptrDownY = 0;
                    break;
            }

            return true;
        }

        return false;
    }

    private boolean detectPullToRefresh(MotionEvent ev) {
        if (pullToRefresh == null) {
            return false;
        }

        if (scrollDetector.theTop()) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downY = ev.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float distance = ev.getRawY() - downY;
                    if (pullToRefresh.getStatus() == PullToRefresh.Status.IDLE) {
                        return distance > 0;
                    }
            }
        }

        return false;
    }

    private void actionPullToRefresh(MotionEvent ev) {
        if (pullToRefresh == null) {
            return;
        }

        if (scrollDetector.theTop()) {
            if (!ptrListenerSet) {
                pullToRefresh.setListener(pullToRefreshListener);
                ptrListenerSet = true;
            }

            float distance;

            switch (ev.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    distance = ev.getRawY() - downY;
                    distance = distance > 0 ? distance : 0;
                    pullToRefresh.pull((int) (distance / fraction));
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    distance = ev.getRawY() - downY;
                    distance = distance > 0 ? distance : 0;
                    pullToRefresh.release((int) (distance / fraction));
                    break;
            }
        }
    }

    private boolean pullToLoad(MotionEvent ev) {
        if (pullToLoad == null) {
            return false;
        }

        if (!ptlListenerSet) {
            pullToLoad.setListener(pullToLoadListener);
            ptlListenerSet = true;
        }

        if (scrollDetector.theEnd() && pullToLoad.getStatus() == PullToLoad.Status.IDLE || pullToLoad.getStatus() == PullToLoad.Status.PULL) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    ptlDownY = ev.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:

                    if (ptlDownY == 0) {
                        ptlDownY = ev.getRawY();
                    }

                    float distance = ptlDownY - ev.getRawY();
                    if (distance > 0) {
                        distance = distance > 0 ? distance : 0;
                        pullToLoad.pull((int) (distance / fraction));
                        ((View) scrollDetector).setPressed(false);
                    } else {
                        pullToLoad.pull(0);
                        ((View) scrollDetector).setPressed(true);
                        return false;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    distance = ptlDownY - ev.getRawY();
                    distance = distance > 0 ? distance : 0;
                    pullToLoad.release((int) (distance / fraction));
                    ((View) scrollDetector).setPressed(true);
                    ptlDownY = 0;
                    break;
            }

            return true;
        }

        return false;
    }

    private boolean detectPullToLoad(MotionEvent ev) {
        if (pullToLoad == null) {
            return false;
        }

        if (scrollDetector.theEnd()) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downY = ev.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float distance = downY - ev.getRawY();
                    if (pullToLoad.getStatus() == PullToLoad.Status.IDLE) {
                        return distance > 0;
                    }
            }
        }

        return false;
    }

    private void actionPullToLoad(MotionEvent ev) {
        if (pullToLoad == null) {
            return;
        }

        if (scrollDetector.theEnd() || pullToLoad.getStatus() == PullToLoad.Status.PULL) {
            if (!ptlListenerSet) {
                pullToLoad.setListener(pullToLoadListener);
                ptlListenerSet = true;
            }

            float distance = 0;

            switch (ev.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    distance = downY - ev.getRawY();
                    distance = distance > 0 ? distance : 0;
                    pullToLoad.pull((int) (distance / fraction));
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    distance = downY - ev.getRawY();
                    distance = distance > 0 ? distance : 0;
                    pullToLoad.release((int) (distance / fraction));
                    break;
            }
        }
    }

    public void stopRefresh() {
        postDelayed(new Runnable() {
            @Override
            public void run() {
                pullToRefresh.finish();
            }
        }, 500);
    }

    public void stopLoad() {
        postDelayed(new Runnable() {
            @Override
            public void run() {
                pullToLoad.finish();
            }
        }, 500);
    }

    public void startRefresh() {
        if (!ptrListenerSet) {
            pullToRefresh.setListener(pullToRefreshListener);
            ptrListenerSet = true;
        }

        pullToRefresh.autoRefresh();
    }

    public void startLoad() {
        if (!ptlListenerSet) {
            pullToLoad.setListener(pullToLoadListener);
            ptlListenerSet = true;
        }

        pullToLoad.autoLoad();
    }
}
